package net.thunderbird.feature.mail.message.list.internal.domain.usecase

import assertk.all
import assertk.assertThat
import assertk.assertions.containsOnly
import assertk.assertions.hasSize
import assertk.assertions.isEmpty
import kotlin.random.Random
import kotlin.test.Test
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import net.thunderbird.core.common.action.SwipeAction
import net.thunderbird.core.common.action.SwipeActions
import net.thunderbird.core.common.appConfig.PlatformConfigProvider
import net.thunderbird.core.preference.GeneralSettings
import net.thunderbird.core.preference.GeneralSettingsManager
import net.thunderbird.core.preference.display.DisplaySettings
import net.thunderbird.core.preference.interaction.KEY_SWIPE_ACTION_LEFT
import net.thunderbird.core.preference.interaction.KEY_SWIPE_ACTION_RIGHT
import net.thunderbird.core.preference.network.NetworkSettings
import net.thunderbird.core.preference.notification.NotificationPreference
import net.thunderbird.core.preference.privacy.PrivacySettings
import net.thunderbird.feature.mail.message.list.internal.fakes.FakeAccount
import net.thunderbird.feature.mail.message.list.internal.fakes.FakeAccountManager

@OptIn(ExperimentalUuidApi::class)
@Suppress("MaxLineLength")
class BuildSwipeActionsTest {
    private val defaultGeneralSettings
        get() = GeneralSettings(
            display = DisplaySettings(),
            network = NetworkSettings(),
            notification = NotificationPreference(),
            privacy = PrivacySettings(),
            platformConfigProvider = FakePlatformConfigProvider(),
        )

    @Test
    fun `invoke should return empty map when empty account uuids is provided`() {
        // Arrange
        val uuids = setOf<String>()
        val testSubject = createTestSubject(
            accountsUuids = List(size = 10) { Uuid.random().toHexString() },
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).isEmpty()
    }

    @Test
    fun `invoke should return map with SwipeActions(ToggleRead, ToggleSelection) when no user preference is stored`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.ToggleRead,
                    rightAction = SwipeAction.ToggleSelection,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with multiple keys when multiple accounts`() {
        // Arrange
        val accountsSize = 10
        val uuids = List(size = accountsSize) { Uuid.random().toHexString() }
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids.toSet(),
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).all {
            hasSize(accountsSize)
            containsOnly(
                elements = uuids
                    .associateWith {
                        SwipeActions(
                            leftAction = SwipeAction.ToggleRead,
                            rightAction = SwipeAction.ToggleSelection,
                        )
                    }
                    .map { it.key to it.value }
                    .toTypedArray(),
            )
        }
    }

    @Test
    fun `invoke should return map with SwipeActions(None, ToggleSelection) when left action is stored as None but right is not`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_LEFT to SwipeAction.None,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.None,
                    rightAction = SwipeAction.ToggleSelection,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with SwipeActions(ToggleRead, Delete) when left action is not stored but right is stored as Delete`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_RIGHT to SwipeAction.Delete,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.ToggleRead,
                    rightAction = SwipeAction.Delete,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with SwipeActions(Archive, Archive) when both stored actions are Archive, account isn't pop3 and has archive folder`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive,
                KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { true },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.Archive,
                    rightAction = SwipeAction.Archive,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with SwipeActions(ArchiveDisabled, ArchiveDisabled) when both stored actions are Archive, account is pop3`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive,
                KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { true },
            hasArchiveFolder = { true },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.ArchiveDisabled,
                    rightAction = SwipeAction.ArchiveDisabled,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with SwipeActions(ArchiveSetupArchiveFolder, ArchiveSetupArchiveFolder) when both stored actions are Archive, account isn't pop3, has not archive folder and shouldShowSetupArchiveFolderDialog is true`() {
        // Arrange
        val uuid = Uuid.random().toHexString()
        val uuids = setOf(uuid)
        val testSubject = createTestSubject(
            initialGeneralSettings = defaultGeneralSettings.copy(
                display = defaultGeneralSettings.display.copy(
                    miscSettings = defaultGeneralSettings.display.miscSettings.copy(
                        shouldShowSetupArchiveFolderDialog = true,
                    ),
                ),
            ),
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive,
                KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 1)
            containsOnly(
                uuid to SwipeActions(
                    leftAction = SwipeAction.ArchiveSetupArchiveFolder,
                    rightAction = SwipeAction.ArchiveSetupArchiveFolder,
                ),
            )
        }
    }

    @Test
    fun `invoke should return map with different SwipeAction Archive when multiple accounts that includes pop3 accounts or accounts without archive folder`() {
        // Arrange
        val uuidPop3 = "pop3-account"
        val uuidWithoutArchiveFolder = "no-archive-folder-account"
        val uuidWithArchiveFolder = "archive-folder-account"
        val uuids = setOf(
            uuidPop3,
            uuidWithoutArchiveFolder,
            uuidWithArchiveFolder,
        )
        val testSubject = createTestSubject(
            initialGeneralSettings = defaultGeneralSettings.copy(
                display = defaultGeneralSettings.display.copy(
                    miscSettings = defaultGeneralSettings.display.miscSettings.copy(
                        shouldShowSetupArchiveFolderDialog = true,
                    ),
                ),
            ),
            accountsUuids = uuids.toList(),
            storageValues = mapOf(
                KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive,
                KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive,
            ),
        )

        // Act
        val actions = testSubject(
            accountUuids = uuids,
            isIncomingServerPop3 = { it.uuid == uuidPop3 },
            hasArchiveFolder = { it.uuid == uuidWithArchiveFolder },
        )

        // Assert
        assertThat(actions).all {
            hasSize(size = 3)
            containsOnly(
                uuidPop3 to SwipeActions(
                    leftAction = SwipeAction.ArchiveDisabled,
                    rightAction = SwipeAction.ArchiveDisabled,
                ),
                uuidWithoutArchiveFolder to SwipeActions(
                    leftAction = SwipeAction.ArchiveSetupArchiveFolder,
                    rightAction = SwipeAction.ArchiveSetupArchiveFolder,
                ),
                uuidWithArchiveFolder to SwipeActions(
                    leftAction = SwipeAction.Archive,
                    rightAction = SwipeAction.Archive,
                ),
            )
        }
    }

    @Test
    fun `invoke should return empty map when account uuid doesn't exists in AccountManager`() {
        // Arrange
        val uuids = List(size = Random.nextInt(from = 1, until = 100)) { Uuid.random().toHexString() }
        val accountManagerUuids =
            List(size = Random.nextInt(from = 1, until = 100)) { Uuid.random().toHexString() } - uuids.toSet()
        val testSubject = createTestSubject(accountsUuids = accountManagerUuids)

        // Act
        val actions = testSubject(
            accountUuids = uuids.toSet(),
            isIncomingServerPop3 = { false },
            hasArchiveFolder = { false },
        )

        // Assert
        assertThat(actions).isEmpty()
    }

    private fun createTestSubject(
        initialGeneralSettings: GeneralSettings = defaultGeneralSettings,
        accountsUuids: List<String>,
        storageValues: Map<String, SwipeAction> = mapOf(),
    ): BuildSwipeActions = BuildSwipeActions(
        generalSettingsManager = FakeGeneralSettingsManager(
            initialGeneralSettings.let { settings ->
                if (storageValues.isNotEmpty() &&
                    (KEY_SWIPE_ACTION_LEFT in storageValues || KEY_SWIPE_ACTION_RIGHT in storageValues)
                ) {
                    val swipeActions = settings.interaction.swipeActions
                    settings.copy(
                        interaction = settings.interaction.copy(
                            swipeActions = swipeActions.copy(
                                leftAction = storageValues[KEY_SWIPE_ACTION_LEFT] ?: swipeActions.leftAction,
                                rightAction = storageValues[KEY_SWIPE_ACTION_RIGHT] ?: swipeActions.rightAction,
                            ),
                        ),
                    )
                } else {
                    settings
                }
            },
        ),
        accountManager = FakeAccountManager(accounts = accountsUuids.map { FakeAccount(uuid = it) }),
    )
}

private class FakeGeneralSettingsManager(
    initialGeneralSettings: GeneralSettings,
) : GeneralSettingsManager {
    private val generalSettings = MutableStateFlow(initialGeneralSettings)
    override fun getSettings(): GeneralSettings = generalSettings.value

    override fun getSettingsFlow(): Flow<GeneralSettings> = generalSettings

    override fun save(config: GeneralSettings) {
        error("not implemented")
    }

    override fun getConfig(): GeneralSettings = generalSettings.value

    override fun getConfigFlow(): Flow<GeneralSettings> = generalSettings
}

private class FakePlatformConfigProvider : PlatformConfigProvider {
    override val isDebug: Boolean
        get() = true
}
